/* ###
 * IP: GHIDRA
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ghidra.app.services;

import java.util.Collection;
import java.util.concurrent.*;
import java.util.function.Function;

import org.apache.commons.lang3.exception.ExceptionUtils;

import ghidra.debug.api.progress.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceInfo;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;

/**
 * A service for publishing and subscribing to tasks and progress notifications.
 * 
 * <p>
 * This is an attempt to de-couple the concepts of task monitoring and task execution. The
 * {@link PluginTool} has a system for submitting background tasks. It queues the task. When it
 * reaches the front of the queue, it creates a {@link TaskMonitor}, starts a thread, and executes
 * the task. Unfortunately, this tightly couples the progress reporting system with the execution
 * model, which is not ideal. Moreover, the task queuing system is the only simple way to obtain a
 * {@link TaskMonitor} with any semblance of central management or consistent presentation.
 * Providers can (and often do) create their own {@link TaskMonitor}s, usually placed at the bottom
 * of the provider when it is, e.g., updating a table.
 * 
 * <p>
 * This service attempts to provide a centralized system for creating and presenting
 * {@link TaskMonitor}s separate from the execution model. No particular execution model is
 * required. Nor is the task implicitly associated to a specific thread. A client may use a single
 * thread for all tasks, a single thread for each task, many threads for a task, etc. In fact, a
 * client could even use an {@link ExecutorService}, without any care to how tasks are executed.
 * Instead, a task need simply request a monitor, pass its handle as needed, and close it when
 * finished. The information generated by such monitors is then forwarded to the subscriber which
 * can determine how to present them.
 */
@ServiceInfo(
	defaultProviderName = "ghidra.app.plugin.core.debug.service.progress.ProgressServicePlugin")
public interface ProgressService {
	/**
	 * Publish a task and create a monitor for it
	 * 
	 * <p>
	 * This and the methods on {@link TaskMonitor} are the mechanism for clients to publish task and
	 * progress information. The monitor returned also extends {@link AutoCloseable}, allowing it to
	 * be used fairly safely when the execution model involves a single thread.
	 * 
	 * <pre>
	 * try (CloseableTaskMonitor monitor = progressService.publishTask()) {
	 * 	// Do the computation and update the monitor accordingly.
	 * }
	 * </pre>
	 * 
	 * <p>
	 * If the above idiom is not used, e.g., because the monitor is passed among several
	 * {@link CompletableFuture}s, the client must take care to close it. While the service may make
	 * some effort to clean up dropped handles, this is just a safeguard to prevent stale monitors
	 * from being presented indefinitely. The service may complain loudly when it detects dropped
	 * monitor handles.
	 * 
	 * @return the monitor
	 */
	CloseableTaskMonitor publishTask();

	/**
	 * Collect all the tasks currently in progress
	 * 
	 * <p>
	 * The subscriber ought to call this immediately after adding its listener, in order to catch up
	 * on tasks already in progress.
	 * 
	 * @return a collection of in-progress monitor proxies
	 */
	Collection<MonitorReceiver> getAllMonitors();

	/**
	 * Subscribe to task and progress events
	 * 
	 * @param listener the listener
	 */
	void addProgressListener(ProgressListener listener);

	/**
	 * Un-subscribe from task and progress events
	 * 
	 * @param listener the listener
	 */
	void removeProgressListener(ProgressListener listener);

	/**
	 * A drop-in replacement for {@link PluginTool#execute(Task)} that publishes progress via the
	 * service rather than displaying a dialog.
	 * 
	 * <p>
	 * In addition to changing how progress is displayed, this also returns a future so that task
	 * completion can be detected by the caller.
	 * 
	 * @param task task to run in a new thread
	 * @return a future which completes when the task is finished
	 */
	default CompletableFuture<Void> execute(Task task) {
		return CompletableFuture.supplyAsync(() -> {
			try (CloseableTaskMonitor monitor = publishTask()) {
				monitor.setCancelEnabled(task.canCancel());
				try {
					task.run(monitor);
				}
				catch (CancelledException e) {
					throw new CancellationException("User cancelled");
				}
				catch (Throwable e) {
					monitor.reportError(e);
					return ExceptionUtils.rethrow(e);
				}
				return null;
			}
		});
	}

	/**
	 * Similar to {@link #execute(Task)}, but for asynchronous methods
	 * 
	 * @param <T> the type of future result
	 * @param canCancel true if the task can be cancelled
	 * @param hasProgress true if the task displays progress
	 * @param isModal true if the task is modal (ignored)
	 * @param futureSupplier the task which returns a future, given the task monitor
	 * @return the future returned by the supplier
	 */
	default <T> CompletableFuture<T> execute(boolean canCancel, boolean hasProgress,
			boolean isModal, Function<TaskMonitor, CompletableFuture<T>> futureSupplier) {
		CloseableTaskMonitor monitor = publishTask();
		monitor.setCancelEnabled(canCancel);
		CompletableFuture<T> future = futureSupplier.apply(monitor);
		future.handle((t, ex) -> {
			monitor.close();
			return null;
		});
		monitor.addCancelledListener(() -> future.cancel(true));
		return future;
	}
}
